import fs from '@ohos.file.fs';
import dict from '../core/dict';
import utils from '../core/utils';
import logger from '../core/logger';
import request from '@ohos.request';
import globalData from '../core/globalData';
import common from '@ohos.app.ability.common';
import cacheInfo from '../models/logic/cacheInfo';
import { ValuesBucket } from '@ohos.data.ValuesBucket';
import downloadInfo from '../models/logic/downloadInfo';
import relationalStore from '@ohos.data.relationalStore';

export default class downloader {
  private context: common.Context;
  private infoArr: cacheInfo[] = [];
  private readonly packName: string = '';
  private static _instance: downloader;

  public static get instance(): downloader {
    return this._instance;
  }

  public static createInstance(context: common.Context): downloader {
    if (this._instance == null)
      this._instance = new downloader(context);
    return this._instance;
  }

  private async init() {
    try {
      if (utils.isEmpty(this.rdbStore)) {
        await relationalStore.getRdbStore(this.context, {
          name: 'CacheDatabase.db',
          securityLevel: relationalStore.SecurityLevel.S1,
          encrypt: false
        }).then((rdbStore: relationalStore.RdbStore) => {
          if (!utils.isEmpty(rdbStore)) {
            rdbStore.executeSql(dict.CACHE_COMIC_TABLE_FORM)
            rdbStore.executeSql(dict.CACHE_EP_TABLE_FORM)
            globalData.cacheRdbStore = rdbStore;
          }
        });
      }
    } catch (e) {
      logger.error(e);
    }
  }

  private get rdbStore() {
    return globalData.cacheRdbStore;
  }

  private constructor(context: common.Context) {
    try {
      this.context = context;
      this.packName = context.getApplicationContext().applicationInfo.name;
      let root = `${context.filesDir}/${this.packName}/download`;
      fs.access(root, (err, res) => {
        if (!err && !res)
          fs.mkdir(root);
      });
      this.init();
    } catch (e) {
      logger.error(e);
    }
  }

  start(info: downloadInfo, urls: string[]) {
    try {
      this.create(info).then((res) => {
        if (res) {
          let pathDir = `${this.context.filesDir}/${this.packName}/download/${info.cid}/${info.epId}`;
          this.download(info, pathDir, urls);
        } else
          utils.showPopup('初始化漫画环境失败');
      });
    } catch (e) {
      logger.error(e);
    }
  }

  private createComic(info: downloadInfo) {
    return new Promise((resolve) => {
      try {
        let obj = {
          'comicId': info.cid,
          'comicName': info.title,
          'cover': info.cover
        };
        this.rdbStore.insert(dict.CACHE_COMIC_TABLE_NAME, obj).then(() => {
          utils.mkdir(`${this.context.filesDir}/${this.packName}/download/${info.cid}`).then((res) => {
            if (!res)
              resolve(false);
            else
              utils.mkdir(`${this.context.filesDir}/${this.packName}/download/${info.cid}/${info.epId}`).then((res) => resolve(res));
          });
        }).catch((err) => {
          logger.error(err);
          resolve(false);
        });
      } catch (e) {
        logger.error(e);
        resolve(false);
      }
    });
  }

  public create(info: downloadInfo): Promise<boolean> {
    return new Promise((resolve) => {
      try {
        this.createComic(info).then((res) => {
          if (!res)
            resolve(false);
          else {
            let obj = {
              'comicId': info.cid,
              'epId': info.epId,
              'title': info.epTitle,
              'success': 0,
              'failed': 0
            };
            this.rdbStore.insert(dict.CACHE_EP_TABLE_NAME, obj)
              .then(() => resolve(true))
              .catch(() => resolve(false));
          }
        });
      } catch (e) {
        logger.error(e);
        resolve(false);
      }
    });
  }

  getAll(): Promise<cacheInfo[]> {
    return new Promise((resolve) => {
      try {
        this.infoArr.length = 0;
        let predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates(dict.CACHE_COMIC_TABLE_NAME);
        predicates.groupBy(["comicId"]);
        this.rdbStore.query(predicates).then((resultSet: relationalStore.ResultSet) => {
          if (resultSet.rowCount > 0) {
            resultSet.goToFirstRow();
            do {
              let comicId: string = resultSet.getString(resultSet.getColumnIndex('comicId'));
              let comicName: string = resultSet.getString(resultSet.getColumnIndex('comicName'));
              let cover: string = resultSet.getString(resultSet.getColumnIndex('cover'));
              this.infoArr.push(new cacheInfo(comicId, comicName, cover));
            } while (resultSet.goToNextRow());
            resultSet.close();
          }
          resolve(this.infoArr);
        });
      } catch (e) {
        logger.error(e);
        resolve([]);
      }
    });
  }

  getEps(cid: string): Promise<cacheInfo> {
    return new Promise((resolve) => {
      try {
        let predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates(dict.CACHE_EP_TABLE_NAME);
        predicates.equalTo("comicId", cid);
        predicates.groupBy(["comicId"]);
        this.rdbStore.query(predicates).then((resultSet: relationalStore.ResultSet) => {
          if (resultSet.rowCount > 0) {
            resultSet.goToFirstRow();
            let info = this.infoArr.find((i) => i.cid == cid);
            do {
              let epId: string = resultSet.getString(resultSet.getColumnIndex('epId'));
              let title: string = resultSet.getString(resultSet.getColumnIndex('title'));
              let success: number = resultSet.getLong(resultSet.getColumnIndex('success'));
              let failed: number = resultSet.getLong(resultSet.getColumnIndex('failed'));
              info.addEp(epId, title, success, failed)
            } while (resultSet.goToNextRow());
            resultSet.close();
            resolve(info);
          } else
            resolve(null);
        });
      } catch (e) {
        logger.error(e);
        resolve(null);
      }
    });
  }

  getEp(cid: string, epId: string): Promise<cacheInfo> {
    return new Promise((resolve) => {
      try {
        let options = {
          "recursion": false,
          "filter": {
            "suffix": [".jpg"]
          }
        };
        let info = this.infoArr.find((i) => i.cid == cid);
        let epInfo = info?.eps.find((i) => i.epId == epId);
        info.currentEp = epInfo;
        let root = `${this.context.filesDir}/${this.packName}/download/${cid}/${epId}`;
        fs.listFile(root, options).then((eps) => {
          for (let ep of eps) {
            if (ep.endsWith('.jpg')) {
              let img = `file:///${root}/${ep}`;
              epInfo.addImg(img);
            }
          }
          resolve(info);
        });
      } catch (e) {
        logger.error(e);
        resolve(null);
      }
    });
  }

  deleteAll(): Promise<boolean> {
    return new Promise((resolve) => {
      try {
        let predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates(dict.CACHE_COMIC_TABLE_NAME);
        let predicates2: relationalStore.RdbPredicates = new relationalStore.RdbPredicates(dict.CACHE_EP_TABLE_NAME);
        this.rdbStore.delete(predicates).then((rows) => {
          if (rows > 0) {
            this.rdbStore.delete(predicates2).then(() => {
              fs.rmdir(`${this.context.filesDir}/${this.packName}/download`).then(() => {
                this.infoArr.length = 0;
                resolve(true);
              }).catch((e) => {
                logger.error(e);
                resolve(false);
              });
            }).catch(() => resolve(false));
          } else
            resolve(false);
        }).catch(() => resolve(false));
      } catch (e) {
        resolve(false);
      }
    });
  }

  deleteComic(cid: string): Promise<boolean> {
    return new Promise((resolve) => {
      try {
        let predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates(dict.CACHE_COMIC_TABLE_NAME);
        predicates.equalTo("comicId", cid);
        this.rdbStore.delete(predicates).then((rows) => {
          if (rows > 0) {
            this.deleteEp(cid)
              .then((res) => resolve(res))
              .catch(() => resolve(false));
          }
        });
      } catch (e) {
        logger.error(e);
        resolve(false);
      }
    });
  }

  deleteEp(cid: string, epId?: string): Promise<boolean> {
    return new Promise((resolve) => {
      try {
        let fd = `${this.context.filesDir}/${this.packName}/download/${cid}`;
        let predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates(dict.CACHE_EP_TABLE_NAME);
        if (!utils.isEmpty(epId)) {
          predicates.equalTo("epId", epId);
          fd = `${this.context.filesDir}/${this.packName}/download/${cid}/${epId}`;
        }
        predicates.equalTo("comicId", cid);
        this.rdbStore.delete(predicates).then((rows) => {
          if (rows > 0) {
            fs.rmdir(fd).then(() => {
              let info = this.infoArr.find((i) => i.cid == cid);
              if (!utils.isEmpty(epId)) {
                let ep = info.eps.find((i) => i.epId == epId);
                utils.remove(info.eps, ep);
              } else
                utils.remove(this.infoArr, info);
              resolve(true);
            }).catch((e) => {
              logger.error(e);
              resolve(false);
            });
          } else
            resolve(false);
        }).catch(() => resolve(false));
      } catch (e) {
        logger.error(e);
        resolve(false);
      }
    });
  }

  private download(info: downloadInfo, fd: string, urls: string[]) {
    try {
      let idx = 0;
      let total = urls.length;
      let notifyId = utils.showNotify('下载中', `共 ${total} / 成功 0 /失败 0`);
      for (let url of urls) {
        let fileDir = `${fd}/image_${idx}.jpg`;
        request.downloadFile(this.context, {
          url: url,
          filePath: fileDir
        }).then((task) => {
          task.on('complete', () => {
            task.off('complete');
            this.update(info, total, notifyId, true);
          });
          task.on('fail', () => {
            task.off('fail');
            this.update(info, total, notifyId, false);
          });
        }).catch((err) => {
          this.update(info, total, notifyId, false);
          this.createTemp(`${fd}/image_${idx}.tmp`);
          logger.error(`Invoke downloadTask failed, code is ${err.code}, message is ${err.message}`);
        });
        idx++;
      }
    } catch (err) {
      logger.error(`Invoke downloadFile failed, code is ${err.code}, message is ${err.message}`);
    }
  }

  private update(di: downloadInfo, total: number, notifyId: number, success: boolean) {
    try {
      let info = this.infoArr.find((i) => i.cid == di.cid);
      if (utils.isEmpty(info)) {
        info = new cacheInfo(di.cid, di.title, di.cover);
        this.infoArr.push(info);
      }
      info.updateEp(di.epId, di.epTitle, notifyId, success, total);
      let predicates = new relationalStore.RdbPredicates(dict.CACHE_EP_TABLE_NAME);
      predicates.equalTo("comicId", di.cid);
      predicates.equalTo("epId", di.epId);
      this.rdbStore.query(predicates).then((resultSet: relationalStore.ResultSet) => {
        if (resultSet.rowCount > 0) {
          resultSet.goToFirstRow();
          let info = this.infoArr.find((i) => i.cid == di.cid);
          let ep = info.eps.find((i) => i.epId == di.epId);
          let obj: ValuesBucket = null;
          let successValue: number = resultSet.getLong(resultSet.getColumnIndex('success'));
          let failedValue: number = resultSet.getLong(resultSet.getColumnIndex('failed'));
          if (success) {
            successValue++;
            obj = {
              success: successValue
            };
          } else {
            failedValue++;
            obj = {
              failed: failedValue
            };
          }
          ep.update(successValue, failedValue);
          let predicates2 = new relationalStore.RdbPredicates(dict.CACHE_EP_TABLE_NAME);
          predicates2.equalTo("comicId", di.cid);
          predicates2.equalTo("epId", di.epId);
          this.rdbStore.update(obj, predicates2);
          resultSet.close();
        }
      });
    } catch (e) {
      logger.error(e);
    }
  }

  private createTemp(filePath: string) {
    try {
      fs.open(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE, (err, file) => {
        if (!err)
          fs.write(file.fd, '').then(() => fs.closeSync(file))
        else
          logger.error(JSON.stringify(err))
      });
    } catch (e) {
      logger.error(e);
    }
  }
}